package au.edu.qut.bpm.genbinder.genetic.genesis;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;

import au.edu.qut.bpm.genbinder.models.Activity;
import au.edu.qut.bpm.genbinder.models.Case;
import au.edu.qut.bpm.genbinder.models.ConcreteEventLog;
import au.edu.qut.bpm.genbinder.models.Resource;

public class InitialLogPopulationFactory extends LogAbstractFactory {
	int safeValidPercentage = 100;
	//boolean performSafetyCheck = true;

	public InitialLogPopulationFactory(ConcreteEventLog log, int safeValidPercentage) {
		super(log);
		this.safeValidPercentage = safeValidPercentage;
	}
	
	public ConcreteEventLog generateRandomCandidate(Random rng) {
		ConcreteEventLog newLog = new ConcreteEventLog(templateLog);
		Set<Activity> scheduledActivities = new HashSet<Activity>();
		ArrayList<Case> alCase = new ArrayList<Case>(newLog.getAbstractLog().getCases());
		
		Collections.shuffle(alCase);
		Random randomGenerator = new Random();
		int randomSafePercentage = randomGenerator.nextInt(100);
		
		for (Case c : alCase) {
			Random random = new Random();
			int randomInt = random.nextInt(10);
			long gap = randomInt;
			
			boolean first = true;
			long placeAfter = 0 + gap;
			if (first)
				placeAfter = c.getArrival() + gap;
			first = false;
			for (Activity a : c.getOrderedTrace()) {
				// Find the smallest start time
				Set<Resource> usedAtProposal = null;
				// Start with possible minimum

				//usedAtProposal = getRandomResourceSet(newLog, a, rng);
				///*
				if (randomSafePercentage >= safeValidPercentage) {
					usedAtProposal = getRandomResourceSet(newLog, a, rng);
				} else {
					usedAtProposal = getRandomPossibleResourceSetForProposedStartTime(newLog, a, placeAfter, rng);
				}
				//*/
				if (usedAtProposal == null) {
					boolean scheduled = false;
					// Use already scheduled activities to discretize problem
					for (Activity oa : scheduledActivities) {
						if (newLog.getActivityEnd(oa) >= placeAfter) {
								usedAtProposal = getRandomPossibleResourceSetForProposedStartTime(newLog, a, newLog.getActivityEnd(oa), rng);
							
							if (usedAtProposal != null) {
								newLog.setActivityStart(a, newLog.getActivityEnd(oa));
								newLog.setActivityResources(a, usedAtProposal);
								scheduled = true;
								break;
							}
						}
					}
					if (!scheduled)
						System.err.println("An activity could not be greedily scheduled: you should panic");
				} else {
					// Good news
					newLog.setActivityStart(a, placeAfter);
					newLog.setActivityResources(a, usedAtProposal);
				}
				Random random2 = new Random();
				int randomInt2 = random2.nextInt(10);
				long gap2 = randomInt2;
				
				placeAfter = newLog.getActivityEnd(a) + gap2;
				scheduledActivities.add(a);
			}
		}
		return newLog;
	}

	/*
	public ConcreteEventLog generateRandomCandidate(Random rng) {
		// Create a new solution (not valid)
		ConcreteEventLog newLog;

		while (true) {
			boolean restart = false;
			newLog = new ConcreteEventLog(templateLog);
			// Randomize the starts
			for (Case c : newLog.getAbstractLog().getCases()) {
				long randomizeAfter = c.getArrival();
				for (Activity a : c.getOrderedTrace()) {
					// TODO: Set better gap?
					long gap = (long) Math.ceil(rng.nextFloat() * a.getDuration() * 2);
					newLog.setActivityStart(a, randomizeAfter + gap);
					randomizeAfter = newLog.getActivityEnd(a);
				}
			}

			// Randomize the resources, restart if this clashes
			for (Case c : newLog.getAbstractLog().getCases()) {
				if (restart)
					break;
				for (Activity a : c.getActivities()) {
					if (restart)
						break;
					Set<Resource> usedAtProposal = getRandomResourceSet(newLog, a, rng);//unsafe
					
					if (usedAtProposal == null) {
						// TODO: Note that this could potentially take very long
						// for tight problems. Maybe devise a better method
						// (combined with fallback from repair approach?)
						restart = true;
					} else {
						newLog.setActivityResources(a, usedAtProposal);
					}
				}
			}
			if (!restart)
				break;

		} // End forever
		
		//Return either fixed or un-fixed logs, depending on the percentage (on a scale of 0-100) of preferred safe & valid logs.
		Random randomGenerator = new Random();
		int randomInt = randomGenerator.nextInt(100);
		
		if (randomInt > safeValidPercentage) {
			return newLog;//remains unsafe and invalid
		} else {
			LogRepairer repairer = new LogRepairer();
			List<ConcreteEventLog> l = new ArrayList<ConcreteEventLog>();
			l.add(newLog);
			l = repairer.apply(l, new Random());
			
			return l.get(0);//make it safe after unsafe operation
		}
	}
	//*/
	@Override
	public ConcreteEventLog generateRandomCandidate(boolean performSafetyCheck, Random rng) {
		return generateRandomCandidate(rng);
	}

	@Override
	public ConcreteEventLog generateRandomCandidate() {
		return generateRandomCandidate(new Random());
	}
}
